﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;

namespace CID.Tests.Support.Configuration.Database
{
    class SnapshotHandler
    {
        #region FileInformation class
        private class FileInformation
        {
            public string Name { get; private set; }
            public string FilePath { get; private set; }
            public string BackupPath { get; private set; }

            public FileInformation(string name, string filePath, string backupPath)
            {
                Name = name;
                FilePath = filePath;
                BackupPath = backupPath;
            }
        }
        #endregion

        private const string MasterDbName = "master";
        private const string Suffix = ".TestsSnapshot";

        private IList<FileInformation> filesInformation;
        private bool isLocalDatabase;
        private string testsDbConnectionString;
        private string testsDbName;
        private string masterDbConnectionString;

        public SnapshotHandler(string connectionString, bool isLocalDatabase)
        {
            filesInformation = new List<FileInformation>();
            this.isLocalDatabase = isLocalDatabase;

            testsDbConnectionString = connectionString;
            using (var connection = GetTestsDbConnection())
            {
                testsDbName = connection.Database;
                InitializeFileInformation(connection);
                connection.Close();
            }
            masterDbConnectionString = testsDbConnectionString.Replace(testsDbName, MasterDbName);
        }

        private void InitializeFileInformation(SqlConnection connection)
        {
            using (var command = connection.CreateCommand())
            {
                command.CommandText = "SELECT [name],[filename] FROM sysfiles";
                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        string name = reader.GetString(0);
                        string filePath = reader.GetString(1);
                        string backupPath = String.Concat(filePath, Suffix);
                        filesInformation.Add(new FileInformation(name, filePath, backupPath));
                    }
                    reader.Close();
                }
            }
        }

        public void TakeSnapshot()
        {
            ManipulateDatabaseFiles((x, y) =>
            {
                if (isLocalDatabase) 
                    CopyFile(x.FilePath, x.BackupPath); 
                else 
                    CopyFile(y, x.FilePath, x.BackupPath);
            });
        }

        public void RollbackToSnapshot()
        {
            ManipulateDatabaseFiles((x, y) =>
            {
                if (isLocalDatabase)
                    CopyFile(x.BackupPath, x.FilePath);
                else
                    CopyFile(y, x.BackupPath, x.FilePath);
            });
        }

        #region Database manipulation
        private void ManipulateDatabaseFiles(Action<FileInformation, IDbConnection> fileAction)
        {
            ClearPools();
            using (var connection = GetMasterDbConnection())
            {
                SqlCommand cmd = connection.CreateCommand();
                cmd.CommandText = String.Format("ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE", testsDbName);
                cmd.ExecuteNonQuery();

                cmd = connection.CreateCommand();
                cmd.CommandText = GetDettachDbCommandText();
                cmd.ExecuteNonQuery();

                foreach (var file in filesInformation)
                    fileAction(file, connection);

                cmd = connection.CreateCommand();
                cmd.CommandText = GetAttachDbCommandText();
                cmd.ExecuteNonQuery();

                cmd = connection.CreateCommand();
                cmd.CommandText = String.Format(@"ALTER DATABASE [{0}] SET MULTI_USER WITH ROLLBACK IMMEDIATE{1}ALTER DATABASE [{0}] SET AUTO_CLOSE OFF", testsDbName, Environment.NewLine);
                cmd.ExecuteNonQuery();
                connection.Close();
            }
            ClearPools();
        }

        private string GetDettachDbCommandText()
        {
            return String.Format("sp_detach_db '{0}', 'true';", testsDbName);
        }

        private string GetAttachDbCommandText()
        {
            var sb = new StringBuilder();
            sb.AppendFormat("sp_attach_db '{0}'", testsDbName);
            foreach (var file in filesInformation)
                sb.AppendFormat(",'{0}'", file.FilePath);
            sb.Append(";");

            return sb.ToString();
        }

        private static void CopyFile(IDbConnection connection, string sourcePath, string destinationPath)
        {
            if (connection.State == ConnectionState.Open)
            {
                using (IDbCommand cmd = connection.CreateCommand())
                {
                    cmd.CommandText = String.Format("EXEC XP_CMDSHELL 'COPY \"{0}\" \"{1}\"'", sourcePath, destinationPath);
                    cmd.ExecuteNonQuery();
                }
            }
        }
        
        private static void CopyFile(string sourcePath, string destinationPath)
        {
            System.IO.File.Copy(sourcePath, destinationPath, true);
        }
        #endregion

        #region DatabaseConnection handling
        private void ClearPools()
        {
            SqlConnection.ClearPool(GetTestsDbConnection());
            SqlConnection.ClearPool(GetMasterDbConnection());
        }

        private SqlConnection GetTestsDbConnection()
        {
            var result = new SqlConnection(testsDbConnectionString);
            result.Open();
            return result;
        }

        private SqlConnection GetMasterDbConnection()
        {
            var result = new SqlConnection(masterDbConnectionString);
            result.Open();
            return result;
        }
        #endregion
    }
}
